;******************************************************************************************************************
; Application Processor Trampoline Code
;
; sets up the AP correctly and signals the BSP when ready
;
; must be flat assembled. linked to 0x2000
;
; Author: Aidan Goddard 1/7/13
;******************************************************************************************************************

[BITS 16]
[ORG 0x2000]

; AP enters here, long jump to align segment registers
START:
	cli
	jmp	0x0000:START16

; pad to allow space for control variables
times	0x100 - 12 - ($ - $$) db 0

;******************************************************************************************************************
; control variables
cpu_ready	dd 0
cpu_count	dd 1	; start at 1 for first AP
cpu_control	dd 0

;******************************************************************************************************************
; main start point
START16:
	; align data segments
	mov	ax, cs
	mov	ds, ax
	mov	es, ax

	; setup stack
	mov	sp, 0x1000

	; set check value at (0x2100 - 4) to 1
	mov	si, cpu_control
	mov	eax, 1
	mov	[si], eax

	; setup for mininal protected mode environment
	lgdt	[prmd_gdt_desc]		; load basic GDT
	mov	eax, cr0		; get cr0
	or	eax, 1			; set protected mode switch
	mov	cr0, eax		; save it back in cr0
	jmp	0x08:START32		; jump to 32 bit segment to enable 32 bit protected mode

;******************************************************************************************************************
[BITS 32]
START32:
	; setup the data segment
	mov	ax, 0x10
	mov	ds, ax

	; now enable long mode
	; the page table is at 0x100000 (1MB)
	mov	edi, 0x100000		; put the plm4 address in edi
	mov	cr3, edi		; give cr3 the location of the page table pml4

	; enable PAE
	mov	eax, cr4
	or	al, 1 << 5
	mov	cr4, eax

	; enable long mode
	mov	ecx, 0xc0000080
	rdmsr
	or	ax, 1 << 8
	wrmsr

	; enable paging
	mov	eax, cr0
	or	eax, 1 << 31
	mov	cr0, eax

	; load the long mode GDT (with offset truncated to 32 bits)
	lgdt	[long_gdt_desc]

	; jump to long mode code
	jmp	0x08:START64

;******************************************************************************************************************
[BITS 64]
START64:
	; now in long mode
	; reload long mode GDT
	; long mode GDT must be reloaded whilst in long mode to ensure that
	; the GDT_base value is not truncated to 32 bits
	lgdt	[long_gdt_desc]

	; setup segments
	mov	ax, 0x30
	mov	ds, ax
	mov	es, ax
	mov	ax, 0x00
	mov	ss, ax

	; get tss for this CPU
	mov	rax, cpu_count
	mov	ecx, [rax]
	shl	ecx, 4
	add	ecx, 0x40
	ltr	cx

	; load the system IDT
	lidt	[long_idt_desc]

	; enable SSE and the like
	mov 	rax, cr0
	and 	ax, 0xfffb		; clear coprocessor emulation CR0.EM
	or 	ax, 0x2			; set coprocessor monitoring CR0.MP
	mov 	cr0, rax
	mov 	rax, cr4
	or 	ax, 3 << 9		; set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
	mov 	cr4, rax

	; enable paging global pages
	mov	rax, cr4
	or	rax, (1 << 7)
	mov	cr4, rax

	; now need to setup the APIC for this CPU
	; hardware enable the APIC
	mov		rcx, 0x1b
	rdmsr
	or		rax, 0x800
	wrmsr

	; first get APIC base address into rsi
	mov	rcx, 0x1b
	rdmsr
	mov	rcx, 0xfffff000
	and	rax, rcx
	mov	rsi, rax

	; set the APIC's spurious interrupt to vector 63 and soft-enable the APIC
	mov	eax, [rsi + 0x0f0]		; first read it
	or	eax, 0x100			; set software enable bit
	and	eax, 0xffffff00			; clear vector
	add	eax, 0x3f			; set vector to 0x3f (63)
	mov	[rsi + 0x0f0], eax		; send it to the APIC

	; set the APIC timer vector register (unmasked, continuous mode to vector 50)
	mov	[rsi + 0x320], DWORD (50 + (1 << 17))

	; set the APIC timer divide register to 16
	mov	[rsi + 0x3e0], DWORD 3

	; get the APIC ticks/sec value
	mov	rdi, 0xe00000 + 0x100000000	; ptr to value
	mov	eax, [rdi]			; get the value
	mov	ecx, 100 * 16			; divide by 100 * 16 to get ticks for 10ms
	xor	edx, edx			; clear rdx
	div	ecx				; get rax = ticks per 10ms
	mov	[rsi + 0x380], eax		; send it to the APIC

	; now need to configure LINTs
	; get base address of system config CPU table and CPU ID number
	mov	rdi, 0xe00000 + 0x100000000 + 0x4300
	mov	rcx, cpu_count
	xor	rax, rax
	mov	eax, [rcx]

	; rax = CPU ID, rdi = base address of table
	; get base address of this entry
	shl	rax, 6				; multiply cpu ID by 64
	add	rdi, rax			; add it to the table base

CONFIGURE_LINTs:
	; rdi = base address of table entry, rsi = APIC base address
	; first check LINT0 type
	mov	eax, [rdi + 16]			; get value 16 bytes into table (LINT0 type) - 5th DWORD
	cmp	eax, 1				; check it for NMI
	jne	LINT1				; if not NMI, move on to LINT1

	; LINT0 is NMI
	; get trigger mode from table and mangle it to get the correct bit value to send to the APIC
	xor	ebx, ebx			; clear ebx - default value is zeroed trigger mode bit
	mov	eax, [rdi + 28]			; get trigger mode value from table
	or	eax, eax			; check it for zero
	jnz	.LINT_tm_0			; trigger mode not equal to zero, need to zero the TM bit

	mov	ebx, 1				; trigger mode is equal to zero, need to set the TM bit

.LINT_tm_0:
	; set value to write to register (trigger mode << 15 + 1 << 10 (type))
	shl	ebx, 15
	add	ebx, (1 << 10)

	; write it to the APIC register
	mov	[rsi + 0x350], ebx

LINT1:
	; now check LINT1 type
	mov	eax, [rdi + 20]			; get value 20 bytes into table (LINT1 type) - 6th DWORD
	cmp	eax, 1				; check it for NMI
	jne	FINISH				; if not NMI, move on

	; LINT1 is NMI
	; get trigger mode from table and mangle it to get the correct bit value to send to the APIC
	xor	ebx, ebx			; clear ebx - default value is zeroed trigger mode bit
	mov	eax, [rdi + 36]			; get trigger mode value from table
	or	eax, eax			; check it for zero
	jnz	.LINT_tm_0			; trigger mode not equal to zero, need to zero the TM bit

	mov	ebx, 1				; trigger mode is equal to zero, need to set the TM bit

.LINT_tm_0:
	; set value to write to register (trigger mode << 15 + 1 << 10 (type))
	shl	ebx, 15
	add	ebx, (1 << 10)

	; write it to the APIC register
	mov	[rsi + 0x350], ebx

FINISH:
	; increase CPU counter
	mov	rax, cpu_count
	inc	DWORD [rax]

	; finished, tell the BSP we're ready
	mov	rax, cpu_ready
	mov	[rax], DWORD 1

	; send EOI to APIC
	mov	rcx, 0x1b
	rdmsr
	mov	rcx, 0xfffff000
	and	rax, rcx
	add	rax, 0xb0
	mov	[rax], DWORD 0

	;str		rax
	;sub		rax, 0x40
	;shr		rax, 3
	;mov		rcx, 0x1000b8000 + (24 * 160)
	;add 	rax, rcx
	;mov 	[rax], BYTE '0'

	; now calculate address of idle thread and load the correct iretq stack variables
	mov		rax, 0x24b000 + 0x100b00000		; idle thread TCB

	xor		rbx, rbx						; clear for stack segment
	push	rbx
	mov 	rbx, [rax + 3168]				; get stack poiner
	push	rbx
	mov 	rbx, [rax + 3080]				; get RFLAGS
	push	rbx
	mov 	rbx, [rax + 3088]				; code segment
	push	rbx
	mov 	rbx, [rax + 3072]				; RIP
	push	rbx

	; perform iretq to enter idle thread
	iretq


;******************************************************************************************************************
	; enable interrupts to enter scheduler
;	sti

	; halt loop wait here until the scheduler is enabled by the BSP
;.halt_loop:
;	hlt
;	jmp	.halt_loop

;******************************************************************************************************************
; data
; protected mode flat GDT
align 16
prmd_gdt_table:
	; null
	dq 0

	; kernel code
	dw 0xFFFF
	dw 0x0000
	db 0x00
	db 10011010b
	db 11001111b
	db 0x00

	; kernel data
	dw 0xFFFF
	dw 0x0000
	db 0x00
	db 10010010b
	db 11001111b
	db 0x00

prmd_gdt_desc:
	dw $ - prmd_gdt_table - 1
	dd prmd_gdt_table


; long mode gdt descriptor
long_gdt_desc:
	.gdt_limit	dw 0x1000
	.gdt_base	dq 0x100701000	;0x701000 + 0x100000000

; long mode idt descriptor
long_idt_desc:
	.idt_limit	dw 0x1000
	.idt_base 	dq 0x100700000	;0x700000 + 0x100000000

;******************************************************************************************************************
; pad to 4k
times	0x1000 - ($ - $$) db 0
